/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.jacusoft.annotation.processor;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;
import org.jacusoft.annotation.FieldVisitor;
import org.jacusoft.annotation.StringFieldVisitor;

/**
 *
 * @author bc548
 */
public abstract class SingleAnnotationProcessor extends AbstractProcessor {
  
  protected static final FieldVisitor FIELD_VISITOR=new FieldVisitor();
  protected static final StringFieldVisitor STRING_FIELD_VISITOR=new StringFieldVisitor();

  private static final String RESOURCE_PATH = "META-INF/resources/";

  protected abstract void processAnnotation(Class annotationClass, Element annotatedElement, RoundEnvironment roundEnv) throws Exception;

  @Override
  public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    //
    Set elements = roundEnv.getElementsAnnotatedWith(this.getAnnotationToProccess());
    for (Element annotatedElement : (Set<Element>) elements) {
      try {
        processAnnotation(this.getAnnotationToProccess(), annotatedElement, roundEnv);
      } catch (Exception e) {
        RuntimeException re = new RuntimeException("Error processing annotation [" + this.getAnnotationToProccess() + "] on element [" + annotatedElement.getSimpleName() + "] .", e);
        this.reportError(re);
      }
    }
    //
    return true;
  }
  
  protected final Class getAnnotationToProccess() {
    Class annotationToProcess = null;
    String annotationName=null;
    //
    if (!this.getSupportedAnnotationTypes().isEmpty()) {
      try {
        annotationName=this.getSupportedAnnotationTypes().iterator().next();
        annotationToProcess = (Class) Class.forName(annotationName);
      } catch (Exception e) {
        RuntimeException re = new RuntimeException("The specified annotation [" + annotationName + "] does not exist.", e);
        this.reportError(re);
      }
    }
    else{
      this.reportMandatoryWarning("There is no specified annotation to process.");
    }
    Object annotationNames[] = this.getSupportedAnnotationTypes().toArray();
    //
    return annotationToProcess;
  }
  
  protected List<Element> getEnclosedElementsAnnotatedWith(Element element,Class annotationClass){
    List<Element> elementsAnnotated=new ArrayList<Element>();
    //
    for(Element enclosedElement:element.getEnclosedElements()){
      if(enclosedElement.getAnnotation(annotationClass)!=null){
        elementsAnnotated.add(enclosedElement);
      }
    }
    //
    return elementsAnnotated;
  }
  
  protected List<VariableElement> getEnclosedFieldsAnnotatedWith(Element element,Class annotationClass){
    List<VariableElement> elementsAnnotated=new ArrayList<VariableElement>();
    //
    for(Element enclosedElement:element.getEnclosedElements()){
      if(enclosedElement.getKind().equals(ElementKind.FIELD)&&(enclosedElement.getAnnotation(annotationClass)!=null)){
        elementsAnnotated.add((VariableElement)enclosedElement);
      }
    }
    //
    return elementsAnnotated;
  }
  
  protected List<ExecutableElement> getEnclosedMethodsAnnotatedWith(Element element,Class annotationClass){
    List<ExecutableElement> elementsAnnotated=new ArrayList<ExecutableElement>();
    //
    for(Element enclosedElement:element.getEnclosedElements()){
      if(enclosedElement.getKind().equals(ElementKind.METHOD)&&(enclosedElement.getAnnotation(annotationClass)!=null)){
        elementsAnnotated.add((ExecutableElement)enclosedElement);
      }
    }
    //
    return elementsAnnotated;
  }
  
  protected List<ExecutableElement> getEnclosedConstructorsAnnotatedWith(Element element,Class annotationClass){
    List<ExecutableElement> elementsAnnotated=new ArrayList<ExecutableElement>();
    //
    for(Element enclosedElement:element.getEnclosedElements()){
      if(enclosedElement.getKind().equals(ElementKind.CONSTRUCTOR)&&(enclosedElement.getAnnotation(annotationClass)!=null)){
        elementsAnnotated.add((ExecutableElement)enclosedElement);
      }
    }
    //
    return elementsAnnotated;
  }
  
  protected List<TypeElement> getEnclosedClassesAnnotatedWith(Element element,Class annotationClass){
    List<TypeElement> elementsAnnotated=new ArrayList<TypeElement>();
    //
    for(Element enclosedElement:element.getEnclosedElements()){
      if(enclosedElement.getKind().equals(ElementKind.CLASS)&&(enclosedElement.getAnnotation(annotationClass)!=null)){
        elementsAnnotated.add((TypeElement)enclosedElement);
      }
    }
    //
    return elementsAnnotated;
  }
  
  protected VariableElement getEnclosedFieldAnnotatedWith(Element element,Class annotationClass){
    return this.getEnclosedFieldsAnnotatedWith(element, annotationClass).get(0);
  }
  
  protected ExecutableElement getEnclosedMethodAnnotatedWith(Element element,Class annotationClass){
    return this.getEnclosedMethodsAnnotatedWith(element, annotationClass).get(0);
  }
  
  protected ExecutableElement getEnclosedConstructorAnnotatedWith(Element element,Class annotationClass){
    return this.getEnclosedConstructorsAnnotatedWith(element, annotationClass).get(0);
  }
  
  protected TypeElement getEnclosedClassAnnotatedWith(Element element,Class annotationClass){
    return this.getEnclosedClassesAnnotatedWith(element, annotationClass).get(0);
  }
  
  protected Object getFieldValue(VariableElement field){
    field.accept(FIELD_VISITOR, null);
    return FIELD_VISITOR.visitVariable(field,null);
  }
  
  protected String getStringFieldValue(VariableElement field){
    field.accept(STRING_FIELD_VISITOR, null);
    return STRING_FIELD_VISITOR.visitVariable(field,null);
  }

  protected InputStream getResourceAsStream(String resourceName) {
    return this.getClass().getClassLoader().getResourceAsStream(RESOURCE_PATH + resourceName);
  }

  protected String getResourceAsText(String resourceName) {
    return this.getResourceAsText(resourceName, "UTF-8");
  }

  protected String getResourceAsText(String resourceName, String charset) {
    String text = null;
    BufferedReader reader = null;
    //
    try {
      reader = new BufferedReader(new InputStreamReader(this.getResourceAsStream(resourceName), charset));
      //
      String line = null;
      do {
        line = reader.readLine();
        //
        if (line != null) {
          if (text == null) {
            text = line;
          }
          text += "\n" + line;
        }
      } while (line != null);
    } catch (Exception e) {
      throw new RuntimeException("Error trying to read resource.", e);
    } finally {
      try {
        reader.close();
      } catch (Exception ignore) {
      }
    }
    //
    return text;
  }

  protected BufferedWriter getSourceFileWriter(String sourceFile) {
    BufferedWriter sourceFileWriter = null;
    try {
      sourceFileWriter = new BufferedWriter(this.processingEnv.getFiler().createSourceFile(sourceFile).openWriter());
    } catch (Exception e) {
      throw new RuntimeException("Error trying to create a writer to source file [" + sourceFile + "].", e);
    }
    return sourceFileWriter;
  }

  protected BufferedWriter getClassFileWriter(String classFile) {
    BufferedWriter classFileWriter = null;
    try {
      classFileWriter = new BufferedWriter(this.processingEnv.getFiler().createClassFile(classFile).openWriter());
    } catch (Exception e) {
      throw new RuntimeException("Error trying to create a writer to class file [" + classFile + "].", e);
    }
    return classFileWriter;
  }

  protected BufferedWriter getResourseFileWriter(String resource) {
    BufferedWriter resourceFileWriter = null;
    try {
      throw new UnsupportedOperationException("Not implemented yet.");
      //resourceFileWriter=new BufferedWriter(this.processingEnv.getFiler().createResource(resource).openWriter());
    } catch (Exception e) {
      throw new RuntimeException("Error trying to create a writer to resource file [" + resource + "].", e);
    }
    //return resourceFileWriter;
  }

  protected void reportNote(Exception e) {
    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, e.getMessage());
    e.printStackTrace();
  }

  protected void reportNote(String message) {
    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
  }

  protected void reportWarning(Exception e) {
    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, e.getMessage());
    e.printStackTrace();
  }

  protected void reportWarning(String message) {
    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message);
  }

  protected void reportMandatoryWarning(Exception e) {
    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, e.getMessage());
    e.printStackTrace();
  }

  protected void reportMandatoryWarning(String message) {
    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING, message);
  }

  protected void reportError(Exception e) {
    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
    e.printStackTrace();
  }

  protected void reportError(String message) {
    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message);
  }

}
